Random Choices

How to pick a number in a range with a given probability? Like, 10% of your spawned entities should be scorable points, 2% special effects and the rest enemies. That was one of the problems I had to solve for my current game, Catch².

My first solution was rather clumsy:

	var type = randi() % 100
	var _square = square.instance()
	if (0 <= type) and (type < 10):
		spawn_point()
	elif (10 <= type) and (type < 12):
		spawn_bonus()
	else:
		spawn_enemy()

This would make 10% of the spawned squares a scorable point, 2% a bonus and the rest (88% if my math is correct) enemy suqares. This solution works fine but has an obvious downside: If I ever needed to fine tune the percentages (and oh did I!), I would have to adjust every number in that long winded if-else-statement. And I can’t just plugin the percentages, they have to be kind of stacked or ranges or whatever you’d like to call that.

So this was not a good solution and there has to be a better way. Turns out: There are many ways. To name only two there is an Alias algorithm or a Vose algorithm. All fairly complicated ways that require a degree in mathematics to understand. And only for such a simple task…

But finally I stumbled upon a method to create a weighted random distribution that even I could understand. I’ve implemented it in Gdscript as follows:

    func random_weighted_distribution(probability):
        var sum = 0
        var r = randf()
        for key in probability:
            sum += probability[key]
            if r <= sum:
            return key

It takes a dictionary of weighted distributions and gives back a random key, according to the weight. Now I only need to define a dictionary with all the probabilities (which have to add up to 1!). After a call the function I just need to match against the return value:

    probability_distribution = {"point": 0.1, "bonus": 0.02, "enemy": 0.88} 
    match random_weighted_distribution(probability_distribution):
        "point": spawn_point()
        "enemy": spawn_enemy()
        "bonus": spawn_bonus()

And voilà! An easy to understand and easy to maintain piece of code!